﻿using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace RaceHFTools.BluetoothLE
{
    public class Device
    {
        public EventHandler OnDisconnect;
        public IDevice Target { get; protected set; }
        public List<IService> Services { get; private set; } = new List<IService>();
        public List<ICharacteristic> Characteristics { get; private set; } = new List<ICharacteristic>();

        public int MTU { get; private set; }
        public const int MTU_DEFAULT = 23;
        public const int MTU_MAX = 247;

        private readonly Controller Controller;

        public bool IsConnect { get => Controller.IsConnect(Target); }

        public Device(Controller controller, IDevice target)
        {
            Target = target;
            Controller = controller;

            controller.OnDisconnect += OnDisconnectHandler;
        }

        ~Device()
        {
            Controller.OnDisconnect -= OnDisconnectHandler;
        }

        public async Task FindServiceAndCharacteristic()
        {
            await RunOnUI(async () =>
            {
                Services.AddRange(await Target.GetServicesAsync());
                foreach (IService service in Services)
                {
                    Characteristics.AddRange(await service.GetCharacteristicsAsync());
                }
            });
        }

        public void UpdateConnInterval(ConnectionInterval conn_interval)
        {
            Target.UpdateConnectionInterval(conn_interval);
        }

        public async Task<int> UpdateMTU(int mtu)
        {
            int rsp_mtu = MTU_DEFAULT;
            int req_mtu = mtu < MTU_DEFAULT ? MTU_DEFAULT :
                          mtu > MTU_MAX ? MTU_MAX :
                          mtu;

            await RunOnUI(async () => rsp_mtu = await Target.RequestMtuAsync(req_mtu));
            MTU = rsp_mtu;

            return rsp_mtu;
        }

        public ICharacteristic FindCharacteristicById(Guid id)
        {
            if (Characteristics != null)
            {
                foreach (ICharacteristic characteristic in Characteristics)
                {
                    if (characteristic.Id == id)
                    {
                        return characteristic;
                    }
                }
            }

            return null;
        }

        public async Task EnableNotify(ICharacteristic characteristic)
        {
            await RunOnUI(async () => await characteristic.StartUpdatesAsync());
        }

        public async Task DisableNotify(ICharacteristic characteristic)
        {
            await RunOnUI(async () => await characteristic.StopUpdatesAsync());
        }

        public async Task Write(ICharacteristic characteristic, byte[] value)
        {
            await RunOnUI(async () => await characteristic.WriteAsync(value));
        }

        public async Task<byte[]> Read(ICharacteristic characteristic)
        {
            return await characteristic.ReadAsync();
        }

        public async Task Disconnect()
        {
            await RunOnUI(async () => await Controller.Disconnect(Target));
        }

        void OnDisconnectHandler(object sender, IDevice device)
        {
            Controller.OnDisconnect -= OnDisconnectHandler;

            if (device == Target)
                OnDisconnect?.Invoke(sender, null);
        }

        async Task RunOnUI<T>(Func<Task<T>> task)
        {
            await Xamarin.Forms.Device.InvokeOnMainThreadAsync(task);

        }

        async Task RunOnUI(Func<Task> task)
        {
            await Xamarin.Forms.Device.InvokeOnMainThreadAsync(task);
        }
    }
}
